from os import system
from socket import socket, gethostbyname, gethostname
from json import loads, dumps
from threading import Thread, Lock
from time import sleep
# import curses
# from curses import initscr,noecho


def log(strr):
    ## # # # ]print(strr, end='')
    return None

# def # log_run(strr):
    # curses.addstr(4,0,'|                      |')
    # curses.refresh()
    # curses.addstr(4,2,strr)
    # curses.refresh()
    # return None

def done():
    print('#done')
    return None

# curses=initscr()

#log('creating lock...')
ThreadLock = Lock()
#done()

#log('creating queue...')
JudgeQueue = [] # 评测代码队列 {'code':'','pid':''}
IpQueue = [] # ip队列 (ip,port)
BackQueue = [] # 回调队列 (ip,port)
#done()


def listen():
    '''
    * 程序第一段:启动监听服务;
    * 这里的监听是监听服务器自身的api接口,最好不要向外网开放.
    * 接收到的数据:评测数据.
    * {pid='',code=''}
    '''
    #log('creating object...')
    sk = socket()
    #done()

    #log('getting address...')
    address = (gethostbyname(gethostname()), 12345)
    #done()
    print('address=', address)

    #log('binding...')
    sk.bind(address)
    #done()

    #log('set listen to 16...')
    sk.listen(16)
    #done()

    print('listening...')
    while(1):
        conn, addr = sk.accept()
        data = conn.recv(4096)
        print('get message=',data)
        # log_run('已经接受评测数据!')
        ThreadLock.acquire()
        print('Data Unlocked')
        try:
            #log('adding to judge queue...')
            JudgeQueue.append(loads(str(data, encoding='utf-8')))
            
            BackQueue.append((addr[0],int(loads(data)['port'])))
            # JudgeQueue.append((data))
            #done()
        finally:
            ThreadLock.release()
            print('Data Unlocked')


def send():
    '''
    * 分发服务;
    * 向活跃评测机列表的第一项发送请求:
    1. 格式 :{pid:'',code:''}
    1. 类型 (Json->str)
    '''
    while(1):
        if len(IpQueue) and len(JudgeQueue):  # 如果活跃评测机队列不空且有评测任务
            ThreadLock.acquire()
            Ip = IpQueue[0]
            del(IpQueue[0])
            Judge = JudgeQueue[0]
            #done()

            #log('del Judge[0]...')
            del(JudgeQueue[0])
            #done()

            if Judge == '':
                continue

            #log('making socket object...')
            sk = socket()
            #done()

            ##log('connecting to ')
            ##log(Ip)
            print('...')
            while True:
                try:
                    sk.connect(Ip)
                    sk.send(bytes(dumps(Judge), encoding='utf-8'))
                    # log_run('向评测机发送请求')
                except ConnectionRefusedError:
                    # 不能连接只能换一个
                    # 重点:评测数据不能丢失,用户最重要!
                    # log_run('can\'t connect.')
                    # 评测信息回到队尾
                    JudgeQueue.append(Judge)
                    break
            ThreadLock.release()
    return None


def back():
    '''
    * 最后一步:结果回调
    * 获取评测机的结果并且回调回服务器api
    '''
    #log('creating object...')
    sk = socket()
    #done()

    #log('getting address...')
    address = (gethostbyname(gethostname()), 12343)
    #done()
    print('address=', address)

    #log('binding...')
    sk.bind(address)
    #done()

    ##log('set listen to 16...')
    sk.listen(16)
    ##done()

    print('listening...')
    while(1):
        conn, addr = sk.accept()
        try:
            data = conn.recv(4096)
        except Exception as e:
            print(e)
            continue
        print('get message back=', data)

        address = BackQueue[0]
        del BackQueue[0]

        skk = socket()
        skk.connect(address)
        skk.send(data)
        # log_run('结果已经回调!')
        skk.close()
    return None


def Judge():
    '''
    评测机接受
    '''
    #log('creating object...')
    sk = socket()
    #done()

    #log('getting address...')
    address = ('', 12344)
    #done()
    print('address=', address)

    #log('binding...')
    sk.bind(address)
    #done()

    #log('set listen to 16...')
    sk.listen(16)
    #done()

    print('listening...')
    while(1):
        conn, addr = sk.accept()
        try:
            data = conn.recv(4096)
        except Exception as e:
            print(e)
            continue
        print('get judge message=', data)
        if not IpQueue.count((addr[0], data)) and int(str(data, encoding='utf-8')) > 0 and int(str(data, encoding='utf-8')) <= 65535:
            # #log('adding...')
            # #log((addr[0], data))
            IpQueue.append((addr[0], int(str(data, encoding='utf-8'))))
            # #done()
            print('已连接评测机!')
            # log_run('connect judge program')

# def showstat() :
#     '''
#     (几乎)实时刷新屏幕并显示详情 刷新一次显示一次(因为线程执行时要加锁)
#     '''
#     historylenJudgeQueue =0
#     historylenIpQueue =0

#     curses.addstr(0,0,' ===新成OJ 评测系统====')
#     curses.addstr(1,0,'|                      |')
#     curses.addstr(2,0,'|                      |')
#     curses.addstr(3,0,' ========信 息=========')
#     curses.addstr(4,0,'|                      |')
#     curses.addstr(5,0,' ====================== ')


#     curses.addstr(1,2,'judge queue length:'+str(historylenJudgeQueue))
#     curses.addstr(2,2,'free judge machine:'+str(historylenIpQueue))
#     curses.refresh()
#     while(1) :
#         if len(JudgeQueue) != historylenJudgeQueue :
#             historylenJudgeQueue = len(JudgeQueue)
#             curses.addstr(1,2,'judge queue length:'+str(historylenJudgeQueue))
#             curses.refresh()
#         if len(IpQueue) != historylenIpQueue:
#             historylenIpQueue = len(IpQueue)
#             curses.addstr(2,2,'free judge machine:'+str(historylenIpQueue))
#             curses.refresh()
#         sleep(0.3)

if __name__ == '__main__':

    #使终端不输出
    system('cls')
    # noecho()

    # 启动监听服务
    ThreadListen = Thread(target=listen)
    ThreadListen.start()

    # 启动分发服务
    ThreadSend = Thread(target=send)
    ThreadSend.start()

    # 启动评测结果回调服务
    ThreadBack = Thread(target=back)
    ThreadBack.start()

    # 启动评测机接收服务
    ThreadJudge = Thread(target=Judge)
    ThreadJudge.start()

    print('系统已启动!')

    # 启动详情显示服务
    # ThreadStat = Thread(target=showstat)
    # ThreadStat.start()

    # log_run('system starts')

'''
这个东西已经有非常好的异常规范能力了
评测机异常退出不会丢失评测数据
'''